/* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 * Copyright (C) <2007> Wim Taymans <wim.taymans@collabora.co.uk>
 * Copyright (C) <2007> Edward Hervey <edward.hervey@collabora.co.uk>
 * Copyright (C) <2007> Jan Schmidt <thaytan@noraisin.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:element-alpha
 * @title: alpha
 *
 * The alpha element adds an alpha channel to a video stream. The values
 * of the alpha channel can be either be set to a constant or can be
 * dynamically calculated via chroma keying, e.g. blue can be set as
 * the transparent color.
 *
 * Sample pipeline:
 * |[
 * gst-launch-1.0 videotestsrc pattern=snow ! mixer.sink_0 \
 *   videotestsrc pattern=smpte75 ! alpha method=green ! mixer.sink_1 \
 *   videomixer name=mixer sink_0::zorder=0 sink_1::zorder=1 ! \
 *   videoconvert ! autovideosink
 * ]| This pipeline adds a alpha channel to the SMPTE color bars
 * with green as the transparent color and overlays the output on
 * top of a snow video stream.
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gstalpha.h"

#include <stdlib.h>
#include <string.h>
#include <math.h>

#ifndef M_PI
#define M_PI  3.14159265358979323846
#endif

/* Generated by -bad/ext/cog/generate_tables */
static const int cog_ycbcr_to_rgb_matrix_8bit_hdtv[] = {
  298, 0, 459, -63514,
  298, -55, -136, 19681,
  298, 541, 0, -73988,
};

static const int cog_ycbcr_to_rgb_matrix_8bit_sdtv[] = {
  298, 0, 409, -57068,
  298, -100, -208, 34707,
  298, 516, 0, -70870,
};

static const gint cog_rgb_to_ycbcr_matrix_8bit_hdtv[] = {
  47, 157, 16, 4096,
  -26, -87, 112, 32768,
  112, -102, -10, 32768,
};

static const gint cog_rgb_to_ycbcr_matrix_8bit_sdtv[] = {
  66, 129, 25, 4096,
  -38, -74, 112, 32768,
  112, -94, -18, 32768,
};

static const gint cog_ycbcr_sdtv_to_ycbcr_hdtv_matrix_8bit[] = {
  256, -30, -53, 10600,
  0, 261, 29, -4367,
  0, 19, 262, -3289,
};

static const gint cog_ycbcr_hdtv_to_ycbcr_sdtv_matrix_8bit[] = {
  256, 25, 49, -9536,
  0, 253, -28, 3958,
  0, -19, 252, 2918,
};

/* Alpha signals and args */
enum
{
  /* FILL ME */
  LAST_SIGNAL
};

#define DEFAULT_METHOD ALPHA_METHOD_SET
#define DEFAULT_ALPHA 1.0
#define DEFAULT_TARGET_R 0
#define DEFAULT_TARGET_G 255
#define DEFAULT_TARGET_B 0
#define DEFAULT_ANGLE 20.0
#define DEFAULT_NOISE_LEVEL 2.0
#define DEFAULT_BLACK_SENSITIVITY 100
#define DEFAULT_WHITE_SENSITIVITY 100
#define DEFAULT_PREFER_PASSTHROUGH FALSE

enum
{
  PROP_0,
  PROP_METHOD,
  PROP_ALPHA,
  PROP_TARGET_R,
  PROP_TARGET_G,
  PROP_TARGET_B,
  PROP_ANGLE,
  PROP_NOISE_LEVEL,
  PROP_BLACK_SENSITIVITY,
  PROP_WHITE_SENSITIVITY,
  PROP_PREFER_PASSTHROUGH
};

static GstStaticPadTemplate gst_alpha_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ AYUV, "
            "ARGB, BGRA, ABGR, RGBA, Y444, xRGB, BGRx, xBGR, "
            "RGBx, RGB, BGR, Y42B, YUY2, YVYU, UYVY, I420, YV12, Y41B } "))
    );

static GstStaticPadTemplate gst_alpha_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ AYUV, "
            "ARGB, BGRA, ABGR, RGBA, Y444, xRGB, BGRx, xBGR, "
            "RGBx, RGB, BGR, Y42B, YUY2, YVYU, UYVY, I420, YV12, " "Y41B } "))
    );

static GstStaticCaps gst_alpha_alpha_caps =
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ AYUV, ARGB, BGRA, ABGR, RGBA }"));

/* FIXME: why do we need our own lock for this? */
#define GST_ALPHA_LOCK(alpha) G_STMT_START { \
  GST_LOG_OBJECT (alpha, "Locking alpha from thread %p", g_thread_self ()); \
  g_mutex_lock (&alpha->lock); \
  GST_LOG_OBJECT (alpha, "Locked alpha from thread %p", g_thread_self ()); \
} G_STMT_END

#define GST_ALPHA_UNLOCK(alpha) G_STMT_START { \
  GST_LOG_OBJECT (alpha, "Unlocking alpha from thread %p", g_thread_self ()); \
  g_mutex_unlock (&alpha->lock); \
} G_STMT_END

static GstCaps *gst_alpha_transform_caps (GstBaseTransform * btrans,
    GstPadDirection direction, GstCaps * caps, GstCaps * filter);
static void gst_alpha_before_transform (GstBaseTransform * btrans,
    GstBuffer * buf);

static gboolean gst_alpha_set_info (GstVideoFilter * filter,
    GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps,
    GstVideoInfo * out_info);
static GstFlowReturn gst_alpha_transform_frame (GstVideoFilter * filter,
    GstVideoFrame * in_frame, GstVideoFrame * out_frame);

static void gst_alpha_init_params_full (GstAlpha * alpha,
    const GstVideoFormatInfo * in_info, const GstVideoFormatInfo * out_info);
static void gst_alpha_init_params (GstAlpha * alpha);
static void gst_alpha_set_process_function (GstAlpha * alpha);
static gboolean gst_alpha_set_process_function_full (GstAlpha * alpha,
    GstVideoInfo * in_info, GstVideoInfo * out_info);

static void gst_alpha_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_alpha_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static void gst_alpha_finalize (GObject * object);

#define gst_alpha_parent_class parent_class
G_DEFINE_TYPE (GstAlpha, gst_alpha, GST_TYPE_VIDEO_FILTER);

#define GST_TYPE_ALPHA_METHOD (gst_alpha_method_get_type())
static GType
gst_alpha_method_get_type (void)
{
  static GType alpha_method_type = 0;
  static const GEnumValue alpha_method[] = {
    {ALPHA_METHOD_SET, "Set/adjust alpha channel", "set"},
    {ALPHA_METHOD_GREEN, "Chroma Key on pure green", "green"},
    {ALPHA_METHOD_BLUE, "Chroma Key on pure blue", "blue"},
    {ALPHA_METHOD_CUSTOM, "Chroma Key on custom RGB values", "custom"},
    {0, NULL, NULL},
  };

  if (!alpha_method_type) {
    alpha_method_type = g_enum_register_static ("GstAlphaMethod", alpha_method);
  }
  return alpha_method_type;
}

static void
gst_alpha_class_init (GstAlphaClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GstElementClass *gstelement_class = (GstElementClass *) klass;
  GstBaseTransformClass *btrans_class = (GstBaseTransformClass *) klass;
  GstVideoFilterClass *vfilter_class = (GstVideoFilterClass *) klass;

  GST_DEBUG_CATEGORY_INIT (gst_alpha_debug, "alpha", 0,
      "alpha - Element for adding alpha channel to streams");

  gobject_class->set_property = gst_alpha_set_property;
  gobject_class->get_property = gst_alpha_get_property;
  gobject_class->finalize = gst_alpha_finalize;

  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_METHOD,
      g_param_spec_enum ("method", "Method",
          "How the alpha channels should be created", GST_TYPE_ALPHA_METHOD,
          DEFAULT_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ALPHA,
      g_param_spec_double ("alpha", "Alpha", "The value for the alpha channel",
          0.0, 1.0, DEFAULT_ALPHA,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_R,
      g_param_spec_uint ("target-r", "Target Red",
          "The red color value for custom RGB chroma keying", 0, 255,
          DEFAULT_TARGET_R,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_G,
      g_param_spec_uint ("target-g", "Target Green",
          "The green color value for custom RGB chroma keying", 0, 255,
          DEFAULT_TARGET_G,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TARGET_B,
      g_param_spec_uint ("target-b", "Target Blue",
          "The blue color value for custom RGB chroma keying", 0, 255,
          DEFAULT_TARGET_B,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ANGLE,
      g_param_spec_float ("angle", "Angle", "Size of the colorcube to change",
          0.0, 90.0, DEFAULT_ANGLE,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_NOISE_LEVEL,
      g_param_spec_float ("noise-level", "Noise Level", "Size of noise radius",
          0.0, 64.0, DEFAULT_NOISE_LEVEL,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass),
      PROP_BLACK_SENSITIVITY, g_param_spec_uint ("black-sensitivity",
          "Black Sensitivity", "Sensitivity to dark colors", 0, 128,
          DEFAULT_BLACK_SENSITIVITY,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass),
      PROP_WHITE_SENSITIVITY, g_param_spec_uint ("white-sensitivity",
          "White Sensitivity", "Sensitivity to bright colors", 0, 128,
          DEFAULT_WHITE_SENSITIVITY,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
  g_object_class_install_property (G_OBJECT_CLASS (klass),
      PROP_PREFER_PASSTHROUGH, g_param_spec_boolean ("prefer-passthrough",
          "Prefer Passthrough",
          "Don't do any processing for alpha=1.0 if possible",
          DEFAULT_PREFER_PASSTHROUGH,
          G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));

  gst_element_class_set_static_metadata (gstelement_class, "Alpha filter",
      "Filter/Effect/Video",
      "Adds an alpha channel to video - uniform or via chroma-keying",
      "Wim Taymans <wim.taymans@gmail.com>\n"
      "Edward Hervey <edward.hervey@collabora.co.uk>\n"
      "Jan Schmidt <thaytan@noraisin.net>");

  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_alpha_sink_template);
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_alpha_src_template);

  btrans_class->before_transform =
      GST_DEBUG_FUNCPTR (gst_alpha_before_transform);
  btrans_class->transform_caps = GST_DEBUG_FUNCPTR (gst_alpha_transform_caps);

  vfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_alpha_set_info);
  vfilter_class->transform_frame =
      GST_DEBUG_FUNCPTR (gst_alpha_transform_frame);
}

static void
gst_alpha_init (GstAlpha * alpha)
{
  alpha->alpha = DEFAULT_ALPHA;
  alpha->method = DEFAULT_METHOD;
  alpha->target_r = DEFAULT_TARGET_R;
  alpha->target_g = DEFAULT_TARGET_G;
  alpha->target_b = DEFAULT_TARGET_B;
  alpha->angle = DEFAULT_ANGLE;
  alpha->noise_level = DEFAULT_NOISE_LEVEL;
  alpha->black_sensitivity = DEFAULT_BLACK_SENSITIVITY;
  alpha->white_sensitivity = DEFAULT_WHITE_SENSITIVITY;

  g_mutex_init (&alpha->lock);
}

static void
gst_alpha_finalize (GObject * object)
{
  GstAlpha *alpha = GST_ALPHA (object);

  g_mutex_clear (&alpha->lock);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_alpha_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstAlpha *alpha = GST_ALPHA (object);
  gboolean reconfigure = FALSE;

  GST_ALPHA_LOCK (alpha);
  switch (prop_id) {
    case PROP_METHOD:{
      gint method = g_value_get_enum (value);

      reconfigure = (method != alpha->method) && (method == ALPHA_METHOD_SET
          || alpha->method == ALPHA_METHOD_SET) && (alpha->alpha == 1.0)
          && (alpha->prefer_passthrough);
      alpha->method = method;

      gst_alpha_set_process_function (alpha);
      gst_alpha_init_params (alpha);
      break;
    }
    case PROP_ALPHA:{
      gdouble a = g_value_get_double (value);

      reconfigure = (a != alpha->alpha) && (a == 1.0 || alpha->alpha == 1.0)
          && (alpha->method == ALPHA_METHOD_SET) && (alpha->prefer_passthrough);
      alpha->alpha = a;
      break;
    }
    case PROP_TARGET_R:
      alpha->target_r = g_value_get_uint (value);
      gst_alpha_init_params (alpha);
      break;
    case PROP_TARGET_G:
      alpha->target_g = g_value_get_uint (value);
      gst_alpha_init_params (alpha);
      break;
    case PROP_TARGET_B:
      alpha->target_b = g_value_get_uint (value);
      gst_alpha_init_params (alpha);
      break;
    case PROP_ANGLE:
      alpha->angle = g_value_get_float (value);
      gst_alpha_init_params (alpha);
      break;
    case PROP_NOISE_LEVEL:
      alpha->noise_level = g_value_get_float (value);
      gst_alpha_init_params (alpha);
      break;
    case PROP_BLACK_SENSITIVITY:
      alpha->black_sensitivity = g_value_get_uint (value);
      break;
    case PROP_WHITE_SENSITIVITY:
      alpha->white_sensitivity = g_value_get_uint (value);
      break;
    case PROP_PREFER_PASSTHROUGH:{
      gboolean prefer_passthrough = g_value_get_boolean (value);

      reconfigure = ((! !prefer_passthrough) != (! !alpha->prefer_passthrough))
          && (alpha->method == ALPHA_METHOD_SET) && (alpha->alpha == 1.0);
      alpha->prefer_passthrough = prefer_passthrough;
      break;
    }
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }

  if (reconfigure)
    gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM_CAST (alpha));

  GST_ALPHA_UNLOCK (alpha);
}

static void
gst_alpha_get_property (GObject * object, guint prop_id, GValue * value,
    GParamSpec * pspec)
{
  GstAlpha *alpha = GST_ALPHA (object);

  switch (prop_id) {
    case PROP_METHOD:
      g_value_set_enum (value, alpha->method);
      break;
    case PROP_ALPHA:
      g_value_set_double (value, alpha->alpha);
      break;
    case PROP_TARGET_R:
      g_value_set_uint (value, alpha->target_r);
      break;
    case PROP_TARGET_G:
      g_value_set_uint (value, alpha->target_g);
      break;
    case PROP_TARGET_B:
      g_value_set_uint (value, alpha->target_b);
      break;
    case PROP_ANGLE:
      g_value_set_float (value, alpha->angle);
      break;
    case PROP_NOISE_LEVEL:
      g_value_set_float (value, alpha->noise_level);
      break;
    case PROP_BLACK_SENSITIVITY:
      g_value_set_uint (value, alpha->black_sensitivity);
      break;
    case PROP_WHITE_SENSITIVITY:
      g_value_set_uint (value, alpha->white_sensitivity);
      break;
    case PROP_PREFER_PASSTHROUGH:
      g_value_set_boolean (value, alpha->prefer_passthrough);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static GstCaps *
gst_alpha_transform_caps (GstBaseTransform * btrans,
    GstPadDirection direction, GstCaps * caps, GstCaps * filter)
{
  GstAlpha *alpha = GST_ALPHA (btrans);
  GstCaps *ret, *tmp, *tmp2;
  GstStructure *structure;
  gint i;

  tmp = gst_caps_new_empty ();

  GST_ALPHA_LOCK (alpha);
  for (i = 0; i < gst_caps_get_size (caps); i++) {
    structure = gst_structure_copy (gst_caps_get_structure (caps, i));

    gst_structure_remove_field (structure, "format");
    gst_structure_remove_field (structure, "colorimetry");
    gst_structure_remove_field (structure, "chroma-site");

    gst_caps_append_structure (tmp, structure);
  }

  if (direction == GST_PAD_SINK) {
    tmp2 = gst_static_caps_get (&gst_alpha_alpha_caps);
    ret = gst_caps_intersect (tmp, tmp2);
    gst_caps_unref (tmp);
    gst_caps_unref (tmp2);
    tmp = ret;
    ret = NULL;

    if (alpha->prefer_passthrough && alpha->method == ALPHA_METHOD_SET
        && alpha->alpha == 1.0) {
      ret = gst_caps_copy (caps);
      gst_caps_append (ret, tmp);
      tmp = NULL;
    } else {
      ret = tmp;
      tmp = NULL;
    }
  } else {
    ret = tmp;
    tmp = NULL;
  }

  GST_DEBUG_OBJECT (alpha,
      "Transformed %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT, caps, ret);

  if (filter) {
    GstCaps *intersection;

    GST_DEBUG_OBJECT (alpha, "Using filter caps %" GST_PTR_FORMAT, filter);
    intersection =
        gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST);
    gst_caps_unref (ret);
    ret = intersection;
    GST_DEBUG_OBJECT (alpha, "Intersection %" GST_PTR_FORMAT, ret);
  }


  GST_ALPHA_UNLOCK (alpha);

  return ret;
}

static gboolean
gst_alpha_set_info (GstVideoFilter * filter,
    GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps,
    GstVideoInfo * out_info)
{
  GstAlpha *alpha = GST_ALPHA (filter);
  gboolean passthrough;

  GST_ALPHA_LOCK (alpha);

  alpha->in_sdtv = in_info->colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_BT601;
  alpha->out_sdtv =
      out_info->colorimetry.matrix == GST_VIDEO_COLOR_MATRIX_BT601;

  passthrough = alpha->prefer_passthrough &&
      GST_VIDEO_INFO_FORMAT (in_info) == GST_VIDEO_INFO_FORMAT (out_info)
      && alpha->in_sdtv == alpha->out_sdtv && alpha->method == ALPHA_METHOD_SET
      && alpha->alpha == 1.0;

  GST_DEBUG_OBJECT (alpha,
      "Setting caps %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT
      " (passthrough: %d)", incaps, outcaps, passthrough);
  gst_base_transform_set_passthrough (GST_BASE_TRANSFORM_CAST (filter),
      passthrough);

  if (!gst_alpha_set_process_function_full (alpha, in_info, out_info)
      && !passthrough)
    goto no_process;

  gst_alpha_init_params_full (alpha, in_info->finfo, out_info->finfo);

  GST_ALPHA_UNLOCK (alpha);

  return TRUE;

  /* ERRORS */
no_process:
  {
    GST_WARNING_OBJECT (alpha,
        "No processing function for this caps and no passthrough mode");
    GST_ALPHA_UNLOCK (alpha);
    return FALSE;
  }
}

/* based on http://www.cs.utah.edu/~michael/chroma/
 */
static inline gint
chroma_keying_yuv (gint a, gint * y, gint * u,
    gint * v, gint cr, gint cb, gint smin, gint smax, guint8 accept_angle_tg,
    guint8 accept_angle_ctg, guint8 one_over_kc, guint8 kfgy_scale, gint8 kg,
    guint noise_level2)
{
  gint tmp, tmp1;
  gint x1, y1;
  gint x, z;
  gint b_alpha;

  /* too dark or too bright, keep alpha */
  if (*y < smin || *y > smax)
    return a;

  /* Convert foreground to XZ coords where X direction is defined by
     the key color */
  tmp = ((*u) * cb + (*v) * cr) >> 7;
  x = CLAMP (tmp, -128, 127);
  tmp = ((*v) * cb - (*u) * cr) >> 7;
  z = CLAMP (tmp, -128, 127);

  /* WARNING: accept angle should never be set greater than "somewhat less
     than 90 degrees" to avoid dealing with negative/infinite tg. In reality,
     80 degrees should be enough if foreground is reasonable. If this seems
     to be a problem, go to alternative ways of checking point position
     (scalar product or line equations). This angle should not be too small
     either to avoid infinite ctg (used to suppress foreground without use of
     division) */

  tmp = (x * accept_angle_tg) >> 4;
  tmp = MIN (tmp, 127);

  if (abs (z) > tmp) {
    /* keep foreground Kfg = 0 */
    return a;
  }
  /* Compute Kfg (implicitly) and Kbg, suppress foreground in XZ coord
     according to Kfg */
  tmp = (z * accept_angle_ctg) >> 4;
  tmp = CLAMP (tmp, -128, 127);
  x1 = abs (tmp);
  y1 = z;

  tmp1 = x - x1;
  tmp1 = MAX (tmp1, 0);
  b_alpha = (tmp1 * one_over_kc) / 2;
  b_alpha = 255 - CLAMP (b_alpha, 0, 255);
  b_alpha = (a * b_alpha) >> 8;

  tmp = (tmp1 * kfgy_scale) >> 4;
  tmp1 = MIN (tmp, 255);

  *y = (*y < tmp1) ? 0 : *y - tmp1;

  /* Convert suppressed foreground back to CbCr */
  tmp = (x1 * cb - y1 * cr) >> 7;
  *u = CLAMP (tmp, -128, 127);

  tmp = (x1 * cr + y1 * cb) >> 7;
  *v = CLAMP (tmp, -128, 127);

  /* Deal with noise. For now, a circle around the key color with
     radius of noise_level treated as exact key color. Introduces
     sharp transitions.
   */
  tmp = z * z + (x - kg) * (x - kg);
  tmp = MIN (tmp, 0xffff);

  if (tmp < noise_level2)
    b_alpha = 0;

  return b_alpha;
}

#define APPLY_MATRIX(m,o,v1,v2,v3) ((m[o*4] * v1 + m[o*4+1] * v2 + m[o*4+2] * v3 + m[o*4+3]) >> 8)

static void
gst_alpha_set_argb_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  gint s_alpha = CLAMP ((gint) (alpha->alpha * 256), 0, 256);
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint matrix[12];
  gint y, u, v;
  gint o[4];

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 3);
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[2] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[3] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  memcpy (matrix,
      alpha->out_sdtv ? cog_rgb_to_ycbcr_matrix_8bit_sdtv :
      cog_rgb_to_ycbcr_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      dest[0] = (src[o[0]] * s_alpha) >> 8;

      y = APPLY_MATRIX (matrix, 0, src[o[1]], src[o[2]], src[o[3]]);
      u = APPLY_MATRIX (matrix, 1, src[o[1]], src[o[2]], src[o[3]]);
      v = APPLY_MATRIX (matrix, 2, src[o[1]], src[o[2]], src[o[3]]);

      dest[1] = y;
      dest[2] = u;
      dest[3] = v;

      dest += 4;
      src += 4;
    }
  }
}

static void
gst_alpha_chroma_key_argb_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint a, y, u, v;
  gint r, g, b;
  gint smin, smax;
  gint pa = CLAMP ((gint) (alpha->alpha * 256), 0, 256);
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;
  gint matrix[12];
  gint o[4];

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 3);
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[2] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[3] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  smin = 128 - alpha->black_sensitivity;
  smax = 128 + alpha->white_sensitivity;

  memcpy (matrix,
      alpha->out_sdtv ? cog_rgb_to_ycbcr_matrix_8bit_sdtv :
      cog_rgb_to_ycbcr_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      a = (src[o[0]] * pa) >> 8;
      r = src[o[1]];
      g = src[o[2]];
      b = src[o[3]];

      y = APPLY_MATRIX (matrix, 0, r, g, b);
      u = APPLY_MATRIX (matrix, 1, r, g, b) - 128;
      v = APPLY_MATRIX (matrix, 2, r, g, b) - 128;

      a = chroma_keying_yuv (a, &y, &u, &v, cr, cb,
          smin, smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);

      u += 128;
      v += 128;

      dest[0] = a;
      dest[1] = y;
      dest[2] = u;
      dest[3] = v;

      src += 4;
      dest += 4;
    }
  }
}

static void
gst_alpha_set_argb_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint s_alpha = CLAMP ((gint) (alpha->alpha * 256), 0, 256);
  gint i, j;
  gint p[4], o[4];

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 3);
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[2] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[3] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      dest[p[0]] = (src[o[0]] * s_alpha) >> 8;

      dest[p[1]] = src[o[1]];
      dest[p[2]] = src[o[2]];
      dest[p[3]] = src[o[3]];

      dest += 4;
      src += 4;
    }
  }
}

static void
gst_alpha_chroma_key_argb_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint a, y, u, v;
  gint r, g, b;
  gint smin, smax;
  gint pa = CLAMP ((gint) (alpha->alpha * 256), 0, 256);
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;
  gint matrix[12], matrix2[12];
  gint p[4], o[4];

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 3);
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[2] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[3] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  smin = 128 - alpha->black_sensitivity;
  smax = 128 + alpha->white_sensitivity;

  memcpy (matrix, cog_rgb_to_ycbcr_matrix_8bit_sdtv, 12 * sizeof (gint));
  memcpy (matrix2, cog_ycbcr_to_rgb_matrix_8bit_sdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      a = (src[o[0]] * pa) >> 8;
      r = src[o[1]];
      g = src[o[2]];
      b = src[o[3]];

      y = APPLY_MATRIX (matrix, 0, r, g, b);
      u = APPLY_MATRIX (matrix, 1, r, g, b) - 128;
      v = APPLY_MATRIX (matrix, 2, r, g, b) - 128;

      a = chroma_keying_yuv (a, &y, &u, &v, cr, cb,
          smin, smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);

      u += 128;
      v += 128;

      r = APPLY_MATRIX (matrix2, 0, y, u, v);
      g = APPLY_MATRIX (matrix2, 1, y, u, v);
      b = APPLY_MATRIX (matrix2, 2, y, u, v);

      dest[p[0]] = a;
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      src += 4;
      dest += 4;
    }
  }
}

static void
gst_alpha_set_ayuv_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint s_alpha = CLAMP ((gint) (alpha->alpha * 256), 0, 256);
  gint y, x;
  gint matrix[12];
  gint r, g, b;
  gint p[4];

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  memcpy (matrix,
      alpha->in_sdtv ? cog_ycbcr_to_rgb_matrix_8bit_sdtv :
      cog_ycbcr_to_rgb_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (y = 0; y < height; y++) {
    for (x = 0; x < width; x++) {
      dest[p[0]] = (src[0] * s_alpha) >> 8;

      r = APPLY_MATRIX (matrix, 0, src[1], src[2], src[3]);
      g = APPLY_MATRIX (matrix, 1, src[1], src[2], src[3]);
      b = APPLY_MATRIX (matrix, 2, src[1], src[2], src[3]);

      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      dest += 4;
      src += 4;
    }
  }
}

static void
gst_alpha_chroma_key_ayuv_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint a, y, u, v;
  gint r, g, b;
  gint smin, smax;
  gint pa = CLAMP ((gint) (alpha->alpha * 256), 0, 256);
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;
  gint matrix[12];
  gint p[4];

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  smin = 128 - alpha->black_sensitivity;
  smax = 128 + alpha->white_sensitivity;

  memcpy (matrix,
      alpha->in_sdtv ? cog_ycbcr_to_rgb_matrix_8bit_sdtv :
      cog_ycbcr_to_rgb_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      a = (src[0] * pa) >> 8;
      y = src[1];
      u = src[2] - 128;
      v = src[3] - 128;

      a = chroma_keying_yuv (a, &y, &u, &v, cr, cb,
          smin, smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);

      u += 128;
      v += 128;

      r = APPLY_MATRIX (matrix, 0, y, u, v);
      g = APPLY_MATRIX (matrix, 1, y, u, v);
      b = APPLY_MATRIX (matrix, 2, y, u, v);

      dest[p[0]] = a;
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      src += 4;
      dest += 4;
    }
  }
}

static void
gst_alpha_set_ayuv_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint s_alpha = CLAMP ((gint) (alpha->alpha * 256), 0, 256);
  gint y, x;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  if (alpha->in_sdtv == alpha->out_sdtv) {
    for (y = 0; y < height; y++) {
      for (x = 0; x < width; x++) {
        dest[0] = (src[0] * s_alpha) >> 8;
        dest[1] = src[1];
        dest[2] = src[2];
        dest[3] = src[3];

        dest += 4;
        src += 4;
      }
    }
  } else {
    gint matrix[12];

    memcpy (matrix,
        alpha->out_sdtv ? cog_ycbcr_hdtv_to_ycbcr_sdtv_matrix_8bit :
        cog_ycbcr_sdtv_to_ycbcr_hdtv_matrix_8bit, 12 * sizeof (gint));

    for (y = 0; y < height; y++) {
      for (x = 0; x < width; x++) {
        dest[0] = (src[0] * s_alpha) >> 8;
        dest[1] = APPLY_MATRIX (matrix, 0, src[1], src[2], src[3]);
        dest[2] = APPLY_MATRIX (matrix, 1, src[1], src[2], src[3]);
        dest[3] = APPLY_MATRIX (matrix, 2, src[1], src[2], src[3]);

        dest += 4;
        src += 4;
      }
    }
  }
}

static void
gst_alpha_chroma_key_ayuv_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint a, y, u, v;
  gint smin, smax;
  gint pa = CLAMP ((gint) (alpha->alpha * 256), 0, 256);
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  smin = 128 - alpha->black_sensitivity;
  smax = 128 + alpha->white_sensitivity;

  if (alpha->in_sdtv == alpha->out_sdtv) {
    for (i = 0; i < height; i++) {
      for (j = 0; j < width; j++) {
        a = (src[0] * pa) >> 8;
        y = src[1];
        u = src[2] - 128;
        v = src[3] - 128;

        a = chroma_keying_yuv (a, &y, &u, &v, cr, cb,
            smin, smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        u += 128;
        v += 128;

        dest[0] = a;
        dest[1] = y;
        dest[2] = u;
        dest[3] = v;

        src += 4;
        dest += 4;
      }
    }
  } else {
    gint matrix[12];

    memcpy (matrix,
        alpha->out_sdtv ? cog_ycbcr_hdtv_to_ycbcr_sdtv_matrix_8bit :
        cog_ycbcr_sdtv_to_ycbcr_hdtv_matrix_8bit, 12 * sizeof (gint));

    for (i = 0; i < height; i++) {
      for (j = 0; j < width; j++) {
        a = (src[0] * pa) >> 8;
        y = APPLY_MATRIX (matrix, 0, src[1], src[2], src[3]);
        u = APPLY_MATRIX (matrix, 1, src[1], src[2], src[3]) - 128;
        v = APPLY_MATRIX (matrix, 2, src[1], src[2], src[3]) - 128;

        a = chroma_keying_yuv (a, &y, &u, &v, cr, cb,
            smin, smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        u += 128;
        v += 128;

        dest[0] = a;
        dest[1] = y;
        dest[2] = u;
        dest[3] = v;

        src += 4;
        dest += 4;
      }
    }
  }
}

static void
gst_alpha_set_rgb_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint s_alpha = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  gint i, j;
  gint matrix[12];
  gint y, u, v;
  gint o[3];
  gint bpp;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  bpp = GST_VIDEO_FRAME_COMP_PSTRIDE (in_frame, 0);
  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[2] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  memcpy (matrix,
      alpha->out_sdtv ? cog_rgb_to_ycbcr_matrix_8bit_sdtv :
      cog_rgb_to_ycbcr_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      dest[0] = s_alpha;

      y = APPLY_MATRIX (matrix, 0, src[o[0]], src[o[1]], src[o[2]]);
      u = APPLY_MATRIX (matrix, 1, src[o[0]], src[o[1]], src[o[2]]);
      v = APPLY_MATRIX (matrix, 2, src[o[0]], src[o[1]], src[o[2]]);

      dest[1] = y;
      dest[2] = u;
      dest[3] = v;

      dest += 4;
      src += bpp;
    }
  }
}

static void
gst_alpha_chroma_key_rgb_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint a, y, u, v;
  gint r, g, b;
  gint smin, smax;
  gint pa = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;
  gint matrix[12];
  gint o[3];
  gint bpp;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  bpp = GST_VIDEO_FRAME_COMP_PSTRIDE (in_frame, 0);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[2] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  smin = 128 - alpha->black_sensitivity;
  smax = 128 + alpha->white_sensitivity;

  memcpy (matrix,
      alpha->out_sdtv ? cog_rgb_to_ycbcr_matrix_8bit_sdtv :
      cog_rgb_to_ycbcr_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      a = pa;
      r = src[o[0]];
      g = src[o[1]];
      b = src[o[2]];

      y = APPLY_MATRIX (matrix, 0, r, g, b);
      u = APPLY_MATRIX (matrix, 1, r, g, b) - 128;
      v = APPLY_MATRIX (matrix, 2, r, g, b) - 128;

      a = chroma_keying_yuv (a, &y, &u, &v, cr, cb,
          smin, smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);

      u += 128;
      v += 128;

      dest[0] = a;
      dest[1] = y;
      dest[2] = u;
      dest[3] = v;

      src += bpp;
      dest += 4;
    }
  }
}

static void
gst_alpha_set_rgb_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint s_alpha = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  gint i, j;
  gint p[4], o[3];
  gint bpp;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  bpp = GST_VIDEO_FRAME_COMP_PSTRIDE (in_frame, 0);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[2] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      dest[p[0]] = s_alpha;

      dest[p[1]] = src[o[0]];
      dest[p[2]] = src[o[1]];
      dest[p[3]] = src[o[2]];

      dest += 4;
      src += bpp;
    }
  }
}

static void
gst_alpha_chroma_key_rgb_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint a, y, u, v;
  gint r, g, b;
  gint smin, smax;
  gint pa = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;
  gint matrix[12], matrix2[12];
  gint p[4], o[3];
  gint bpp;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  bpp = GST_VIDEO_FRAME_COMP_PSTRIDE (in_frame, 0);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[2] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  smin = 128 - alpha->black_sensitivity;
  smax = 128 + alpha->white_sensitivity;

  memcpy (matrix, cog_rgb_to_ycbcr_matrix_8bit_sdtv, 12 * sizeof (gint));
  memcpy (matrix2, cog_ycbcr_to_rgb_matrix_8bit_sdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      a = pa;
      r = src[o[0]];
      g = src[o[1]];
      b = src[o[2]];

      y = APPLY_MATRIX (matrix, 0, r, g, b);
      u = APPLY_MATRIX (matrix, 1, r, g, b) - 128;
      v = APPLY_MATRIX (matrix, 2, r, g, b) - 128;

      a = chroma_keying_yuv (a, &y, &u, &v, cr, cb,
          smin, smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);

      u += 128;
      v += 128;

      r = APPLY_MATRIX (matrix2, 0, y, u, v);
      g = APPLY_MATRIX (matrix2, 1, y, u, v);
      b = APPLY_MATRIX (matrix2, 2, y, u, v);

      dest[p[0]] = a;
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      src += bpp;
      dest += 4;
    }
  }
}

static void
gst_alpha_set_planar_yuv_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  guint8 *dest;
  gint width, height;
  gint b_alpha = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  const guint8 *srcY, *srcY_tmp;
  const guint8 *srcU, *srcU_tmp;
  const guint8 *srcV, *srcV_tmp;
  gint i, j;
  gint y_stride, uv_stride;
  gint v_subs, h_subs;

  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  y_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 0);
  uv_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 1);

  srcY_tmp = srcY = GST_VIDEO_FRAME_COMP_DATA (in_frame, 0);
  srcU_tmp = srcU = GST_VIDEO_FRAME_COMP_DATA (in_frame, 1);
  srcV_tmp = srcV = GST_VIDEO_FRAME_COMP_DATA (in_frame, 2);

  switch (GST_VIDEO_FRAME_FORMAT (in_frame)) {
    case GST_VIDEO_FORMAT_I420:
    case GST_VIDEO_FORMAT_YV12:
      v_subs = h_subs = 2;
      break;
    case GST_VIDEO_FORMAT_Y444:
      v_subs = h_subs = 1;
      break;
    case GST_VIDEO_FORMAT_Y42B:
      v_subs = 1;
      h_subs = 2;
      break;
    case GST_VIDEO_FORMAT_Y41B:
      v_subs = 1;
      h_subs = 4;
      break;
    default:
      g_assert_not_reached ();
      return;
  }

  if (alpha->in_sdtv == alpha->out_sdtv) {
    for (i = 0; i < height; i++) {
      for (j = 0; j < width; j++) {
        dest[0] = b_alpha;
        dest[1] = srcY[0];
        dest[2] = srcU[0];
        dest[3] = srcV[0];

        dest += 4;
        srcY++;
        if ((j + 1) % h_subs == 0) {
          srcU++;
          srcV++;
        }
      }

      srcY_tmp = srcY = srcY_tmp + y_stride;
      if ((i + 1) % v_subs == 0) {
        srcU_tmp = srcU = srcU_tmp + uv_stride;
        srcV_tmp = srcV = srcV_tmp + uv_stride;
      } else {
        srcU = srcU_tmp;
        srcV = srcV_tmp;
      }
    }
  } else {
    gint matrix[12];
    gint a, y, u, v;

    memcpy (matrix,
        alpha->out_sdtv ? cog_ycbcr_hdtv_to_ycbcr_sdtv_matrix_8bit :
        cog_ycbcr_sdtv_to_ycbcr_hdtv_matrix_8bit, 12 * sizeof (gint));

    for (i = 0; i < height; i++) {
      for (j = 0; j < width; j++) {
        a = b_alpha;
        y = srcY[0];
        u = srcU[0];
        v = srcV[0];

        dest[0] = a;
        dest[1] = APPLY_MATRIX (matrix, 0, y, u, v);
        dest[2] = APPLY_MATRIX (matrix, 1, y, u, v);
        dest[3] = APPLY_MATRIX (matrix, 2, y, u, v);

        dest += 4;
        srcY++;
        if ((j + 1) % h_subs == 0) {
          srcU++;
          srcV++;
        }
      }

      srcY_tmp = srcY = srcY_tmp + y_stride;
      if ((i + 1) % v_subs == 0) {
        srcU_tmp = srcU = srcU_tmp + uv_stride;
        srcV_tmp = srcV = srcV_tmp + uv_stride;
      } else {
        srcU = srcU_tmp;
        srcV = srcV_tmp;
      }
    }
  }
}

static void
gst_alpha_chroma_key_planar_yuv_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  guint8 *dest;
  gint width, height;
  gint b_alpha = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  const guint8 *srcY, *srcY_tmp;
  const guint8 *srcU, *srcU_tmp;
  const guint8 *srcV, *srcV_tmp;
  gint i, j;
  gint a, y, u, v;
  gint y_stride, uv_stride;
  gint v_subs, h_subs;
  gint smin = 128 - alpha->black_sensitivity;
  gint smax = 128 + alpha->white_sensitivity;
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;

  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  y_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 0);
  uv_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 1);

  srcY_tmp = srcY = GST_VIDEO_FRAME_COMP_DATA (in_frame, 0);
  srcU_tmp = srcU = GST_VIDEO_FRAME_COMP_DATA (in_frame, 1);
  srcV_tmp = srcV = GST_VIDEO_FRAME_COMP_DATA (in_frame, 2);

  switch (GST_VIDEO_FRAME_FORMAT (in_frame)) {
    case GST_VIDEO_FORMAT_I420:
    case GST_VIDEO_FORMAT_YV12:
      v_subs = h_subs = 2;
      break;
    case GST_VIDEO_FORMAT_Y444:
      v_subs = h_subs = 1;
      break;
    case GST_VIDEO_FORMAT_Y42B:
      v_subs = 1;
      h_subs = 2;
      break;
    case GST_VIDEO_FORMAT_Y41B:
      v_subs = 1;
      h_subs = 4;
      break;
    default:
      g_assert_not_reached ();
      return;
  }

  if (alpha->in_sdtv == alpha->out_sdtv) {
    for (i = 0; i < height; i++) {
      for (j = 0; j < width; j++) {
        a = b_alpha;
        y = srcY[0];
        u = srcU[0] - 128;
        v = srcV[0] - 128;

        a = chroma_keying_yuv (a, &y, &u, &v, cr, cb, smin,
            smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        u += 128;
        v += 128;

        dest[0] = a;
        dest[1] = y;
        dest[2] = u;
        dest[3] = v;

        dest += 4;
        srcY++;
        if ((j + 1) % h_subs == 0) {
          srcU++;
          srcV++;
        }
      }

      srcY_tmp = srcY = srcY_tmp + y_stride;
      if ((i + 1) % v_subs == 0) {
        srcU_tmp = srcU = srcU_tmp + uv_stride;
        srcV_tmp = srcV = srcV_tmp + uv_stride;
      } else {
        srcU = srcU_tmp;
        srcV = srcV_tmp;
      }
    }
  } else {
    gint matrix[12];

    memcpy (matrix,
        alpha->out_sdtv ? cog_ycbcr_hdtv_to_ycbcr_sdtv_matrix_8bit :
        cog_ycbcr_sdtv_to_ycbcr_hdtv_matrix_8bit, 12 * sizeof (gint));

    for (i = 0; i < height; i++) {
      for (j = 0; j < width; j++) {
        a = b_alpha;
        y = APPLY_MATRIX (matrix, 0, srcY[0], srcU[0], srcV[0]);
        u = APPLY_MATRIX (matrix, 1, srcY[0], srcU[0], srcV[0]) - 128;
        v = APPLY_MATRIX (matrix, 2, srcY[0], srcU[0], srcV[0]) - 128;

        a = chroma_keying_yuv (a, &y, &u, &v, cr, cb, smin,
            smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        dest[0] = a;
        dest[1] = y;
        dest[2] = u + 128;
        dest[3] = v + 128;

        dest += 4;
        srcY++;
        if ((j + 1) % h_subs == 0) {
          srcU++;
          srcV++;
        }
      }

      srcY_tmp = srcY = srcY_tmp + y_stride;
      if ((i + 1) % v_subs == 0) {
        srcU_tmp = srcU = srcU_tmp + uv_stride;
        srcV_tmp = srcV = srcV_tmp + uv_stride;
      } else {
        srcU = srcU_tmp;
        srcV = srcV_tmp;
      }
    }
  }
}

static void
gst_alpha_set_planar_yuv_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  guint8 *dest;
  gint width, height;
  gint b_alpha = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  const guint8 *srcY, *srcY_tmp;
  const guint8 *srcU, *srcU_tmp;
  const guint8 *srcV, *srcV_tmp;
  gint i, j;
  gint y_stride, uv_stride;
  gint v_subs, h_subs;
  gint matrix[12];
  gint a, y, u, v;
  gint r, g, b;
  gint p[4];

  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  y_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 0);
  uv_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 1);

  srcY_tmp = srcY = GST_VIDEO_FRAME_COMP_DATA (in_frame, 0);
  srcU_tmp = srcU = GST_VIDEO_FRAME_COMP_DATA (in_frame, 1);
  srcV_tmp = srcV = GST_VIDEO_FRAME_COMP_DATA (in_frame, 2);

  switch (GST_VIDEO_FRAME_FORMAT (in_frame)) {
    case GST_VIDEO_FORMAT_I420:
    case GST_VIDEO_FORMAT_YV12:
      v_subs = h_subs = 2;
      break;
    case GST_VIDEO_FORMAT_Y444:
      v_subs = h_subs = 1;
      break;
    case GST_VIDEO_FORMAT_Y42B:
      v_subs = 1;
      h_subs = 2;
      break;
    case GST_VIDEO_FORMAT_Y41B:
      v_subs = 1;
      h_subs = 4;
      break;
    default:
      g_assert_not_reached ();
      return;
  }

  memcpy (matrix,
      alpha->in_sdtv ? cog_ycbcr_to_rgb_matrix_8bit_sdtv :
      cog_ycbcr_to_rgb_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      a = b_alpha;
      y = srcY[0];
      u = srcU[0];
      v = srcV[0];

      dest[p[0]] = a;
      r = APPLY_MATRIX (matrix, 0, y, u, v);
      g = APPLY_MATRIX (matrix, 1, y, u, v);
      b = APPLY_MATRIX (matrix, 2, y, u, v);
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      dest += 4;
      srcY++;
      if ((j + 1) % h_subs == 0) {
        srcU++;
        srcV++;
      }
    }

    srcY_tmp = srcY = srcY_tmp + y_stride;
    if ((i + 1) % v_subs == 0) {
      srcU_tmp = srcU = srcU_tmp + uv_stride;
      srcV_tmp = srcV = srcV_tmp + uv_stride;
    } else {
      srcU = srcU_tmp;
      srcV = srcV_tmp;
    }
  }
}

static void
gst_alpha_chroma_key_planar_yuv_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  guint8 *dest;
  gint width, height;
  gint b_alpha = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  const guint8 *srcY, *srcY_tmp;
  const guint8 *srcU, *srcU_tmp;
  const guint8 *srcV, *srcV_tmp;
  gint i, j;
  gint a, y, u, v;
  gint r, g, b;
  gint y_stride, uv_stride;
  gint v_subs, h_subs;
  gint smin = 128 - alpha->black_sensitivity;
  gint smax = 128 + alpha->white_sensitivity;
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;
  gint matrix[12];
  gint p[4];

  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  y_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 0);
  uv_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 1);

  srcY_tmp = srcY = GST_VIDEO_FRAME_COMP_DATA (in_frame, 0);
  srcU_tmp = srcU = GST_VIDEO_FRAME_COMP_DATA (in_frame, 1);
  srcV_tmp = srcV = GST_VIDEO_FRAME_COMP_DATA (in_frame, 2);

  switch (GST_VIDEO_FRAME_FORMAT (in_frame)) {
    case GST_VIDEO_FORMAT_I420:
    case GST_VIDEO_FORMAT_YV12:
      v_subs = h_subs = 2;
      break;
    case GST_VIDEO_FORMAT_Y444:
      v_subs = h_subs = 1;
      break;
    case GST_VIDEO_FORMAT_Y42B:
      v_subs = 1;
      h_subs = 2;
      break;
    case GST_VIDEO_FORMAT_Y41B:
      v_subs = 1;
      h_subs = 4;
      break;
    default:
      g_assert_not_reached ();
      return;
  }

  memcpy (matrix,
      alpha->in_sdtv ? cog_ycbcr_to_rgb_matrix_8bit_sdtv :
      cog_ycbcr_to_rgb_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    for (j = 0; j < width; j++) {
      a = b_alpha;
      y = srcY[0];
      u = srcU[0] - 128;
      v = srcV[0] - 128;

      a = chroma_keying_yuv (a, &y, &u, &v, cr, cb, smin,
          smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);

      u += 128;
      v += 128;

      dest[p[0]] = a;
      r = APPLY_MATRIX (matrix, 0, y, u, v);
      g = APPLY_MATRIX (matrix, 1, y, u, v);
      b = APPLY_MATRIX (matrix, 2, y, u, v);
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      dest += 4;
      srcY++;
      if ((j + 1) % h_subs == 0) {
        srcU++;
        srcV++;
      }
    }

    srcY_tmp = srcY = srcY_tmp + y_stride;
    if ((i + 1) % v_subs == 0) {
      srcU_tmp = srcU = srcU_tmp + uv_stride;
      srcV_tmp = srcV = srcV_tmp + uv_stride;
    } else {
      srcU = srcU_tmp;
      srcV = srcV_tmp;
    }
  }
}

static void
gst_alpha_set_packed_422_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint s_alpha = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  gint i, j;
  gint y, u, v;
  gint p[4];                    /* Y U Y V */
  gint src_stride;
  const guint8 *src_tmp;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  src_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 0);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  p[2] = p[0] + 2;
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  if (alpha->in_sdtv != alpha->out_sdtv) {
    gint matrix[12];

    memcpy (matrix,
        alpha->in_sdtv ? cog_ycbcr_sdtv_to_ycbcr_hdtv_matrix_8bit :
        cog_ycbcr_hdtv_to_ycbcr_sdtv_matrix_8bit, 12 * sizeof (gint));

    for (i = 0; i < height; i++) {
      src_tmp = src;

      for (j = 0; j < width - 1; j += 2) {
        dest[0] = s_alpha;
        dest[4] = s_alpha;

        y = APPLY_MATRIX (matrix, 0, src[p[0]], src[p[1]], src[p[3]]);
        u = APPLY_MATRIX (matrix, 1, src[p[0]], src[p[1]], src[p[3]]);
        v = APPLY_MATRIX (matrix, 2, src[p[0]], src[p[1]], src[p[3]]);

        dest[1] = y;
        dest[2] = u;
        dest[3] = v;

        y = APPLY_MATRIX (matrix, 0, src[p[2]], src[p[1]], src[p[3]]);
        u = APPLY_MATRIX (matrix, 1, src[p[2]], src[p[1]], src[p[3]]);
        v = APPLY_MATRIX (matrix, 2, src[p[2]], src[p[1]], src[p[3]]);

        dest[5] = y;
        dest[6] = u;
        dest[7] = v;

        dest += 8;
        src += 4;
      }

      if (j == width - 1) {
        dest[0] = s_alpha;

        y = APPLY_MATRIX (matrix, 0, src[p[0]], src[p[1]], src[p[3]]);
        u = APPLY_MATRIX (matrix, 1, src[p[0]], src[p[1]], src[p[3]]);
        v = APPLY_MATRIX (matrix, 2, src[p[0]], src[p[1]], src[p[3]]);

        dest[1] = y;
        dest[2] = u;
        dest[3] = v;

        dest += 4;
      }

      src = src_tmp + src_stride;
    }
  } else {
    for (i = 0; i < height; i++) {
      src_tmp = src;

      for (j = 0; j < width - 1; j += 2) {
        dest[0] = s_alpha;
        dest[4] = s_alpha;

        y = src[p[0]];
        u = src[p[1]];
        v = src[p[3]];

        dest[1] = y;
        dest[2] = u;
        dest[3] = v;

        y = src[p[2]];

        dest[5] = y;
        dest[6] = u;
        dest[7] = v;

        dest += 8;
        src += 4;
      }

      if (j == width - 1) {
        dest[0] = s_alpha;

        y = src[p[0]];
        u = src[p[1]];
        v = src[p[3]];

        dest[1] = y;
        dest[2] = u;
        dest[3] = v;

        dest += 4;
      }

      src = src_tmp + src_stride;
    }
  }
}

static void
gst_alpha_chroma_key_packed_422_ayuv (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint a, y, u, v;
  gint smin, smax;
  gint pa = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;
  gint p[4];                    /* Y U Y V */
  gint src_stride;
  const guint8 *src_tmp;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  src_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 0);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  p[2] = p[0] + 2;
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  smin = 128 - alpha->black_sensitivity;
  smax = 128 + alpha->white_sensitivity;

  if (alpha->in_sdtv != alpha->out_sdtv) {
    gint matrix[12];

    memcpy (matrix,
        alpha->in_sdtv ? cog_ycbcr_sdtv_to_ycbcr_hdtv_matrix_8bit :
        cog_ycbcr_hdtv_to_ycbcr_sdtv_matrix_8bit, 12 * sizeof (gint));

    for (i = 0; i < height; i++) {
      src_tmp = src;

      for (j = 0; j < width - 1; j += 2) {
        y = APPLY_MATRIX (matrix, 0, src[p[0]], src[p[1]], src[p[3]]);
        u = APPLY_MATRIX (matrix, 1, src[p[0]], src[p[1]], src[p[3]]) - 128;
        v = APPLY_MATRIX (matrix, 2, src[p[0]], src[p[1]], src[p[3]]) - 128;

        a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
            smin, smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        dest[0] = a;
        dest[1] = y;
        dest[2] = u + 128;
        dest[3] = v + 128;

        y = APPLY_MATRIX (matrix, 0, src[p[2]], src[p[1]], src[p[3]]);
        u = APPLY_MATRIX (matrix, 1, src[p[2]], src[p[1]], src[p[3]]) - 128;
        v = APPLY_MATRIX (matrix, 2, src[p[2]], src[p[1]], src[p[3]]) - 128;

        a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
            smin, smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        dest[4] = a;
        dest[5] = y;
        dest[6] = u + 128;
        dest[7] = v + 128;

        dest += 8;
        src += 4;
      }

      if (j == width - 1) {
        y = APPLY_MATRIX (matrix, 0, src[p[0]], src[p[1]], src[p[3]]);
        u = APPLY_MATRIX (matrix, 1, src[p[0]], src[p[1]], src[p[3]]) - 128;
        v = APPLY_MATRIX (matrix, 2, src[p[0]], src[p[1]], src[p[3]]) - 128;

        a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
            smin, smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        dest[0] = a;
        dest[1] = y;
        dest[2] = u + 128;
        dest[3] = v + 128;

        dest += 4;
      }

      src = src_tmp + src_stride;
    }
  } else {
    for (i = 0; i < height; i++) {
      src_tmp = src;

      for (j = 0; j < width - 1; j += 2) {
        y = src[p[0]];
        u = src[p[1]] - 128;
        v = src[p[3]] - 128;

        a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
            smin, smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        dest[0] = a;
        dest[1] = y;
        dest[2] = u + 128;
        dest[3] = v + 128;

        y = src[p[2]];
        u = src[p[1]] - 128;
        v = src[p[3]] - 128;

        a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
            smin, smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        dest[4] = a;
        dest[5] = y;
        dest[6] = u + 128;
        dest[7] = v + 128;

        dest += 8;
        src += 4;
      }

      if (j == width - 1) {
        y = src[p[0]];
        u = src[p[1]] - 128;
        v = src[p[3]] - 128;

        a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
            smin, smax, accept_angle_tg, accept_angle_ctg,
            one_over_kc, kfgy_scale, kg, noise_level2);

        dest[0] = a;
        dest[1] = y;
        dest[2] = u + 128;
        dest[3] = v + 128;

        dest += 4;
      }

      src = src_tmp + src_stride;
    }
  }
}

static void
gst_alpha_set_packed_422_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint s_alpha = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  gint i, j;
  gint p[4], o[4];
  gint src_stride;
  const guint8 *src_tmp;
  gint matrix[12];
  gint r, g, b;

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  src_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 0);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[2] = o[0] + 2;
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[3] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  memcpy (matrix,
      alpha->in_sdtv ? cog_ycbcr_to_rgb_matrix_8bit_sdtv :
      cog_ycbcr_to_rgb_matrix_8bit_hdtv, 12 * sizeof (gint));

  for (i = 0; i < height; i++) {
    src_tmp = src;

    for (j = 0; j < width - 1; j += 2) {
      r = APPLY_MATRIX (matrix, 0, src[o[0]], src[o[1]], src[o[3]]);
      g = APPLY_MATRIX (matrix, 1, src[o[0]], src[o[1]], src[o[3]]);
      b = APPLY_MATRIX (matrix, 2, src[o[0]], src[o[1]], src[o[3]]);

      dest[p[0]] = s_alpha;
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      r = APPLY_MATRIX (matrix, 0, src[o[2]], src[o[1]], src[o[3]]);
      g = APPLY_MATRIX (matrix, 1, src[o[2]], src[o[1]], src[o[3]]);
      b = APPLY_MATRIX (matrix, 2, src[o[2]], src[o[1]], src[o[3]]);

      dest[4 + p[0]] = s_alpha;
      dest[4 + p[1]] = CLAMP (r, 0, 255);
      dest[4 + p[2]] = CLAMP (g, 0, 255);
      dest[4 + p[3]] = CLAMP (b, 0, 255);

      dest += 8;
      src += 4;
    }

    if (j == width - 1) {
      r = APPLY_MATRIX (matrix, 0, src[o[0]], src[o[1]], src[o[3]]);
      g = APPLY_MATRIX (matrix, 1, src[o[0]], src[o[1]], src[o[3]]);
      b = APPLY_MATRIX (matrix, 2, src[o[0]], src[o[1]], src[o[3]]);

      dest[p[0]] = s_alpha;
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      dest += 4;
    }

    src = src_tmp + src_stride;
  }
}

static void
gst_alpha_chroma_key_packed_422_argb (const GstVideoFrame * in_frame,
    GstVideoFrame * out_frame, GstAlpha * alpha)
{
  const guint8 *src;
  guint8 *dest;
  gint width, height;
  gint i, j;
  gint a, y, u, v;
  gint r, g, b;
  gint smin, smax;
  gint pa = CLAMP ((gint) (alpha->alpha * 255), 0, 255);
  gint8 cb = alpha->cb, cr = alpha->cr;
  gint8 kg = alpha->kg;
  guint8 accept_angle_tg = alpha->accept_angle_tg;
  guint8 accept_angle_ctg = alpha->accept_angle_ctg;
  guint8 one_over_kc = alpha->one_over_kc;
  guint8 kfgy_scale = alpha->kfgy_scale;
  guint noise_level2 = alpha->noise_level2;
  gint p[4], o[4];
  gint src_stride;
  const guint8 *src_tmp;
  gint matrix[12];

  src = GST_VIDEO_FRAME_PLANE_DATA (in_frame, 0);
  dest = GST_VIDEO_FRAME_PLANE_DATA (out_frame, 0);

  width = GST_VIDEO_FRAME_WIDTH (in_frame);
  height = GST_VIDEO_FRAME_HEIGHT (in_frame);

  src_stride = GST_VIDEO_FRAME_COMP_STRIDE (in_frame, 0);

  o[0] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 0);
  o[2] = o[0] + 2;
  o[1] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 1);
  o[3] = GST_VIDEO_FRAME_COMP_POFFSET (in_frame, 2);

  p[0] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 3);
  p[1] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 0);
  p[2] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 1);
  p[3] = GST_VIDEO_FRAME_COMP_POFFSET (out_frame, 2);

  memcpy (matrix,
      alpha->in_sdtv ? cog_ycbcr_to_rgb_matrix_8bit_sdtv :
      cog_ycbcr_to_rgb_matrix_8bit_hdtv, 12 * sizeof (gint));

  smin = 128 - alpha->black_sensitivity;
  smax = 128 + alpha->white_sensitivity;

  for (i = 0; i < height; i++) {
    src_tmp = src;

    for (j = 0; j < width - 1; j += 2) {
      y = src[o[0]];
      u = src[o[1]] - 128;
      v = src[o[3]] - 128;

      a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
          smin, smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);
      u += 128;
      v += 128;

      r = APPLY_MATRIX (matrix, 0, y, u, v);
      g = APPLY_MATRIX (matrix, 1, y, u, v);
      b = APPLY_MATRIX (matrix, 2, y, u, v);

      dest[p[0]] = a;
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      y = src[o[2]];
      u = src[o[1]] - 128;
      v = src[o[3]] - 128;

      a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
          smin, smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);
      u += 128;
      v += 128;

      r = APPLY_MATRIX (matrix, 0, y, u, v);
      g = APPLY_MATRIX (matrix, 1, y, u, v);
      b = APPLY_MATRIX (matrix, 2, y, u, v);

      dest[4 + p[0]] = a;
      dest[4 + p[1]] = CLAMP (r, 0, 255);
      dest[4 + p[2]] = CLAMP (g, 0, 255);
      dest[4 + p[3]] = CLAMP (b, 0, 255);

      dest += 8;
      src += 4;
    }

    if (j == width - 1) {
      y = src[o[0]];
      u = src[o[1]] - 128;
      v = src[o[3]] - 128;

      a = chroma_keying_yuv (pa, &y, &u, &v, cr, cb,
          smin, smax, accept_angle_tg, accept_angle_ctg,
          one_over_kc, kfgy_scale, kg, noise_level2);
      u += 128;
      v += 128;

      r = APPLY_MATRIX (matrix, 0, y, u, v);
      g = APPLY_MATRIX (matrix, 1, y, u, v);
      b = APPLY_MATRIX (matrix, 2, y, u, v);

      dest[p[0]] = a;
      dest[p[1]] = CLAMP (r, 0, 255);
      dest[p[2]] = CLAMP (g, 0, 255);
      dest[p[3]] = CLAMP (b, 0, 255);

      dest += 4;
    }

    src = src_tmp + src_stride;
  }
}

/* Protected with the alpha lock */
static void
gst_alpha_init_params_full (GstAlpha * alpha,
    const GstVideoFormatInfo * in_info, const GstVideoFormatInfo * out_info)
{
  gfloat kgl;
  gfloat tmp;
  gfloat tmp1, tmp2;
  gfloat y;
  guint target_r = alpha->target_r;
  guint target_g = alpha->target_g;
  guint target_b = alpha->target_b;
  const gint *matrix;

  switch (alpha->method) {
    case ALPHA_METHOD_GREEN:
      target_r = 0;
      target_g = 255;
      target_b = 0;
      break;
    case ALPHA_METHOD_BLUE:
      target_r = 0;
      target_g = 0;
      target_b = 255;
      break;
    default:
      break;
  }

  /* RGB->RGB: convert to SDTV YUV, chroma keying, convert back
   * YUV->RGB: chroma keying, convert to RGB
   * RGB->YUV: convert to YUV, chroma keying
   * YUV->YUV: convert matrix, chroma keying
   */
  if (GST_VIDEO_FORMAT_INFO_IS_RGB (in_info)
      && GST_VIDEO_FORMAT_INFO_IS_RGB (out_info))
    matrix = cog_rgb_to_ycbcr_matrix_8bit_sdtv;
  else if (GST_VIDEO_FORMAT_INFO_IS_YUV (in_info)
      && GST_VIDEO_FORMAT_INFO_IS_RGB (out_info))
    matrix =
        (alpha->in_sdtv) ? cog_rgb_to_ycbcr_matrix_8bit_sdtv :
        cog_rgb_to_ycbcr_matrix_8bit_hdtv;
  else if (GST_VIDEO_FORMAT_INFO_IS_RGB (in_info)
      && GST_VIDEO_FORMAT_INFO_IS_YUV (out_info))
    matrix =
        (alpha->out_sdtv) ? cog_rgb_to_ycbcr_matrix_8bit_sdtv :
        cog_rgb_to_ycbcr_matrix_8bit_hdtv;
  else                          /* yuv -> yuv */
    matrix =
        (alpha->out_sdtv) ? cog_rgb_to_ycbcr_matrix_8bit_sdtv :
        cog_rgb_to_ycbcr_matrix_8bit_hdtv;

  y = (matrix[0] * ((gint) target_r) +
      matrix[1] * ((gint) target_g) +
      matrix[2] * ((gint) target_b) + matrix[3]) >> 8;
  /* Cb,Cr without offset here because the chroma keying
   * works with them being in range [-128,127]
   */
  tmp1 =
      (matrix[4] * ((gint) target_r) +
      matrix[5] * ((gint) target_g) + matrix[6] * ((gint) target_b)) >> 8;
  tmp2 =
      (matrix[8] * ((gint) target_r) +
      matrix[9] * ((gint) target_g) + matrix[10] * ((gint) target_b)) >> 8;

  kgl = sqrt (tmp1 * tmp1 + tmp2 * tmp2);
  alpha->cb = 127 * (tmp1 / kgl);
  alpha->cr = 127 * (tmp2 / kgl);

  tmp = 15 * tan (M_PI * alpha->angle / 180);
  tmp = MIN (tmp, 255);
  alpha->accept_angle_tg = tmp;
  tmp = 15 / tan (M_PI * alpha->angle / 180);
  tmp = MIN (tmp, 255);
  alpha->accept_angle_ctg = tmp;
  tmp = 1 / (kgl);
  alpha->one_over_kc = (gint) (255 * 2 * tmp - 255);
  tmp = 15 * y / kgl;
  tmp = MIN (tmp, 255);
  alpha->kfgy_scale = tmp;
  alpha->kg = MIN (kgl, 127);

  alpha->noise_level2 = alpha->noise_level * alpha->noise_level;
}

static void
gst_alpha_init_params (GstAlpha * alpha)
{
  const GstVideoFormatInfo *finfo_in, *finfo_out;

  finfo_in = GST_VIDEO_FILTER (alpha)->in_info.finfo;
  finfo_out = GST_VIDEO_FILTER (alpha)->out_info.finfo;

  if (finfo_in != NULL && finfo_out != NULL) {
    gst_alpha_init_params_full (alpha, finfo_in, finfo_out);
  } else {
    GST_DEBUG_OBJECT (alpha, "video formats not set yet");
  }
}

/* Protected with the alpha lock */
static gboolean
gst_alpha_set_process_function_full (GstAlpha * alpha, GstVideoInfo * in_info,
    GstVideoInfo * out_info)
{
  alpha->process = NULL;

  switch (alpha->method) {
    case ALPHA_METHOD_SET:
      switch (GST_VIDEO_INFO_FORMAT (out_info)) {
        case GST_VIDEO_FORMAT_AYUV:
          switch (GST_VIDEO_INFO_FORMAT (in_info)) {
            case GST_VIDEO_FORMAT_AYUV:
              alpha->process = gst_alpha_set_ayuv_ayuv;
              break;
            case GST_VIDEO_FORMAT_Y444:
            case GST_VIDEO_FORMAT_Y42B:
            case GST_VIDEO_FORMAT_I420:
            case GST_VIDEO_FORMAT_YV12:
            case GST_VIDEO_FORMAT_Y41B:
              alpha->process = gst_alpha_set_planar_yuv_ayuv;
              break;
            case GST_VIDEO_FORMAT_YUY2:
            case GST_VIDEO_FORMAT_YVYU:
            case GST_VIDEO_FORMAT_UYVY:
              alpha->process = gst_alpha_set_packed_422_ayuv;
              break;
            case GST_VIDEO_FORMAT_ARGB:
            case GST_VIDEO_FORMAT_ABGR:
            case GST_VIDEO_FORMAT_RGBA:
            case GST_VIDEO_FORMAT_BGRA:
              alpha->process = gst_alpha_set_argb_ayuv;
              break;
            case GST_VIDEO_FORMAT_xRGB:
            case GST_VIDEO_FORMAT_xBGR:
            case GST_VIDEO_FORMAT_RGBx:
            case GST_VIDEO_FORMAT_BGRx:
            case GST_VIDEO_FORMAT_RGB:
            case GST_VIDEO_FORMAT_BGR:
              alpha->process = gst_alpha_set_rgb_ayuv;
              break;
            default:
              break;
          }
          break;
        case GST_VIDEO_FORMAT_ARGB:
        case GST_VIDEO_FORMAT_ABGR:
        case GST_VIDEO_FORMAT_RGBA:
        case GST_VIDEO_FORMAT_BGRA:
          switch (GST_VIDEO_INFO_FORMAT (in_info)) {
            case GST_VIDEO_FORMAT_AYUV:
              alpha->process = gst_alpha_set_ayuv_argb;
              break;
            case GST_VIDEO_FORMAT_Y444:
            case GST_VIDEO_FORMAT_Y42B:
            case GST_VIDEO_FORMAT_I420:
            case GST_VIDEO_FORMAT_YV12:
            case GST_VIDEO_FORMAT_Y41B:
              alpha->process = gst_alpha_set_planar_yuv_argb;
              break;
            case GST_VIDEO_FORMAT_YUY2:
            case GST_VIDEO_FORMAT_YVYU:
            case GST_VIDEO_FORMAT_UYVY:
              alpha->process = gst_alpha_set_packed_422_argb;
              break;
            case GST_VIDEO_FORMAT_ARGB:
            case GST_VIDEO_FORMAT_ABGR:
            case GST_VIDEO_FORMAT_RGBA:
            case GST_VIDEO_FORMAT_BGRA:
              alpha->process = gst_alpha_set_argb_argb;
              break;
            case GST_VIDEO_FORMAT_xRGB:
            case GST_VIDEO_FORMAT_xBGR:
            case GST_VIDEO_FORMAT_RGBx:
            case GST_VIDEO_FORMAT_BGRx:
            case GST_VIDEO_FORMAT_RGB:
            case GST_VIDEO_FORMAT_BGR:
              alpha->process = gst_alpha_set_rgb_argb;
              break;
            default:
              break;
          }
          break;
        default:
          break;
      }
      break;
    case ALPHA_METHOD_GREEN:
    case ALPHA_METHOD_BLUE:
    case ALPHA_METHOD_CUSTOM:
      switch (GST_VIDEO_INFO_FORMAT (out_info)) {
        case GST_VIDEO_FORMAT_AYUV:
          switch (GST_VIDEO_INFO_FORMAT (in_info)) {
            case GST_VIDEO_FORMAT_AYUV:
              alpha->process = gst_alpha_chroma_key_ayuv_ayuv;
              break;
            case GST_VIDEO_FORMAT_Y444:
            case GST_VIDEO_FORMAT_Y42B:
            case GST_VIDEO_FORMAT_I420:
            case GST_VIDEO_FORMAT_YV12:
            case GST_VIDEO_FORMAT_Y41B:
              alpha->process = gst_alpha_chroma_key_planar_yuv_ayuv;
              break;
            case GST_VIDEO_FORMAT_YUY2:
            case GST_VIDEO_FORMAT_YVYU:
            case GST_VIDEO_FORMAT_UYVY:
              alpha->process = gst_alpha_chroma_key_packed_422_ayuv;
              break;
            case GST_VIDEO_FORMAT_ARGB:
            case GST_VIDEO_FORMAT_ABGR:
            case GST_VIDEO_FORMAT_RGBA:
            case GST_VIDEO_FORMAT_BGRA:
              alpha->process = gst_alpha_chroma_key_argb_ayuv;
              break;
            case GST_VIDEO_FORMAT_xRGB:
            case GST_VIDEO_FORMAT_xBGR:
            case GST_VIDEO_FORMAT_RGBx:
            case GST_VIDEO_FORMAT_BGRx:
            case GST_VIDEO_FORMAT_RGB:
            case GST_VIDEO_FORMAT_BGR:
              alpha->process = gst_alpha_chroma_key_rgb_ayuv;
              break;
            default:
              break;
          }
          break;
        case GST_VIDEO_FORMAT_ARGB:
        case GST_VIDEO_FORMAT_ABGR:
        case GST_VIDEO_FORMAT_RGBA:
        case GST_VIDEO_FORMAT_BGRA:
          switch (GST_VIDEO_INFO_FORMAT (in_info)) {
            case GST_VIDEO_FORMAT_AYUV:
              alpha->process = gst_alpha_chroma_key_ayuv_argb;
              break;
            case GST_VIDEO_FORMAT_Y444:
            case GST_VIDEO_FORMAT_Y42B:
            case GST_VIDEO_FORMAT_I420:
            case GST_VIDEO_FORMAT_YV12:
            case GST_VIDEO_FORMAT_Y41B:
              alpha->process = gst_alpha_chroma_key_planar_yuv_argb;
              break;
            case GST_VIDEO_FORMAT_YUY2:
            case GST_VIDEO_FORMAT_YVYU:
            case GST_VIDEO_FORMAT_UYVY:
              alpha->process = gst_alpha_chroma_key_packed_422_argb;
              break;
            case GST_VIDEO_FORMAT_ARGB:
            case GST_VIDEO_FORMAT_ABGR:
            case GST_VIDEO_FORMAT_RGBA:
            case GST_VIDEO_FORMAT_BGRA:
              alpha->process = gst_alpha_chroma_key_argb_argb;
              break;
            case GST_VIDEO_FORMAT_xRGB:
            case GST_VIDEO_FORMAT_xBGR:
            case GST_VIDEO_FORMAT_RGBx:
            case GST_VIDEO_FORMAT_BGRx:
            case GST_VIDEO_FORMAT_RGB:
            case GST_VIDEO_FORMAT_BGR:
              alpha->process = gst_alpha_chroma_key_rgb_argb;
              break;
            default:
              break;
          }
          break;
        default:
          break;
      }
      break;
    default:
      break;
  }
  return alpha->process != NULL;
}

static void
gst_alpha_set_process_function (GstAlpha * alpha)
{
  GstVideoInfo *info_in, *info_out;

  info_in = &GST_VIDEO_FILTER (alpha)->in_info;
  info_out = &GST_VIDEO_FILTER (alpha)->out_info;

  if (info_in->finfo != NULL && info_out->finfo != NULL) {
    gst_alpha_set_process_function_full (alpha, info_in, info_out);
  } else {
    GST_DEBUG_OBJECT (alpha, "video formats not set yet");
  }
}

static void
gst_alpha_before_transform (GstBaseTransform * btrans, GstBuffer * buf)
{
  GstAlpha *alpha = GST_ALPHA (btrans);
  GstClockTime timestamp;

  timestamp = gst_segment_to_stream_time (&btrans->segment, GST_FORMAT_TIME,
      GST_BUFFER_TIMESTAMP (buf));
  GST_LOG ("Got stream time of %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp));
  if (GST_CLOCK_TIME_IS_VALID (timestamp))
    gst_object_sync_values (GST_OBJECT (alpha), timestamp);
}

static GstFlowReturn
gst_alpha_transform_frame (GstVideoFilter * filter, GstVideoFrame * in_frame,
    GstVideoFrame * out_frame)
{
  GstAlpha *alpha = GST_ALPHA (filter);

  GST_ALPHA_LOCK (alpha);

  if (G_UNLIKELY (!alpha->process))
    goto not_negotiated;

  alpha->process (in_frame, out_frame, alpha);

  GST_ALPHA_UNLOCK (alpha);

  return GST_FLOW_OK;

  /* ERRORS */
not_negotiated:
  {
    GST_ERROR_OBJECT (alpha, "Not negotiated yet");
    GST_ALPHA_UNLOCK (alpha);
    return GST_FLOW_NOT_NEGOTIATED;
  }
}

static gboolean
plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "alpha", GST_RANK_NONE, GST_TYPE_ALPHA);
}

GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    alpha,
    "adds an alpha channel to video - constant or via chroma-keying",
    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
